home *** CD-ROM | disk | FTP | other *** search
/ Clickx 65 / Clickx 65.iso / software / internet / xmarks / xmarks-3.1.1.xpi / chrome / content / foxmarks-password.js < prev    next >
Encoding:
JavaScript  |  2009-05-05  |  28.5 KB  |  800 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-password.js: component that implements the details of 
  5.  password sync.
  6.  
  7.  */
  8.  
  9.  
  10. function NiceProcess(iterator, funcDo, funcDone){
  11.     var callback = {
  12.         notify: function(timer){
  13.             try {
  14.                 var s = Date.now();
  15.                 while (!iterator.done() && Date.now() - s < 100) {
  16.                     funcDo(iterator);
  17.                 }
  18.                 if(iterator.done()){
  19.                     // dump("ms: " + (Date.now() - s) + "\n");
  20.                         funcDone(iterator);
  21.                 } else {
  22.                     // dump("ms: " + (Date.now() - s) + "\n");
  23.                         timer.initWithCallback(callback, 10,
  24.                             Ci.nsITimer.TYPE_ONE_SHOT);
  25.                 }
  26.             }catch(e){
  27.                 funcDone(iterator, e);
  28.             }
  29.         }
  30.     };
  31.     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  32.     timer.initWithCallback(callback, 10,
  33.         Ci.nsITimer.TYPE_ONE_SHOT);
  34. }
  35.  
  36. function PasswordDatasource(){
  37.     this.Cc = Components.classes;
  38.     this.Ci = Components.interfaces;
  39.  
  40.     this.JSON = this.Cc["@mozilla.org/dom/json;1"].createInstance(this.Ci.nsIJSON);
  41.  
  42.     this.encryptor = CreateAESManager();
  43. }
  44.  
  45. PasswordDatasource.prototype = {
  46.     DiscontinuityPrompt: function() {
  47.         var sb = Bundle().GetStringFromName;
  48.  
  49.         // get a reference to the prompt service component.
  50.         var ps = this.Cc["@mozilla.org/embedcomp/prompt-service;1"].
  51.           getService(this.Ci.nsIPromptService);
  52.  
  53.         var flags = ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0 +
  54.               ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 +
  55.               ps.BUTTON_TITLE_CANCEL    * ps.BUTTON_POS_2;
  56.  
  57.         rv = ps.confirmEx(null, sb("passworddisc.title"), sb("passworddisc.body"), flags,
  58.             sb("disc.merge"), sb("disc.download"), null, null, {});
  59.  
  60.         return rv;
  61.     },
  62.     // 2 - server, 1 -local, other-cancel
  63.     _conflictDialog: function(dest, src){
  64.         var retval = { button: 0 };
  65.         var data = {
  66.             local: { 
  67.                 site: src.hostname,
  68.                 username: src.username,
  69.                 password: src.password
  70.             },
  71.             server: {
  72.                 site: dest.hostname,
  73.                 username: dest.username,
  74.                 password: dest.password
  75.             }
  76.         };
  77.         var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
  78.             getService(Ci.nsIWindowMediator).
  79.             getMostRecentWindow(null);
  80.         
  81.         if (!topwin) {
  82.             LogWrite("conflictDialog: Couldn't find a topwin!");
  83.             throw 4;
  84.         }
  85.         var win = topwin.openDialog(
  86.             "chrome://foxmarks/content/foxmarks-passwordconflict.xul", "_blank",
  87.             "chrome,dialog,modal,centerscreen", data, retval);
  88.         if(!retval.button) {
  89.             throw 2;
  90.         }
  91.        
  92.        return retval.button;
  93.     },
  94.     ClobberDialog: function(len){
  95.         
  96.         if(len > 1)
  97.             return true;
  98.         var sb = Bundle().GetStringFromName;
  99.  
  100.         // get a reference to the prompt service component.
  101.         var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  102.           getService(Ci.nsIPromptService);
  103.  
  104.         var flags = ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_0 +
  105.               ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 +
  106.               ps.BUTTON_TITLE_IS_STRING    * ps.BUTTON_POS_2;
  107.  
  108.         rv = ps.confirmEx(null, sb("pwclobber.title"), sb("pwclobber.body"), flags,
  109.             null, sb("pwclobber.disable"), sb("pwclobber.server"),  null, {});
  110.  
  111.         if(rv == 1){
  112.             gSettings.setSyncEnabled("passwords", false);
  113.         }
  114.         return rv == 2;
  115.     },
  116.     syncType: "passwords",
  117.     getBaselineName: function(){
  118.         return "xmarks-password-baseline-" + gSettings.hash + "json";
  119.     },
  120.     BaselineLoaded: function(baseline, callback) {
  121.         var self = this;
  122.         baseline.OnTree(
  123.             function(nid){
  124.                 self.decode(baseline.Node(nid, true, false));
  125.             },
  126.             function(){
  127.                 callback(0);
  128.             }
  129.         );
  130.     },
  131.     encrypt: function(str){
  132.         var result = this.encryptor.encrypt(gSettings.pin, str);
  133.         if(gSettings.forceGC) {
  134.             Components.utils.forceGC();
  135.         }
  136.         return result;
  137.     },
  138.     decrypt: function(str){
  139.         var result =  this.encryptor.decrypt(gSettings.pin, str);
  140.         var goodPIN = result.indexOf('password') >=  0;
  141.  
  142.         // If we're doing a merge from a reset pin, there will be
  143.         // two pins floating around
  144.         if(!goodPIN && this._oldpin !== undefined){
  145.             result =  this.encryptor.decrypt(this._oldpin, str);
  146.             goodPIN = result.indexOf('password') >=  0;
  147.         }
  148.  
  149.         while(!goodPIN){
  150.             var newpin = this.getValidPin();
  151.             gSettings.pin = newpin;
  152.             result =  this.encryptor.decrypt(gSettings.pin, str);
  153.             goodPIN = result.indexOf('password') >=  0;
  154.             if(!goodPIN){
  155.                 FoxmarksAlert(Bundle().GetStringFromName("error.pinInvalid"));
  156.             }
  157.         }
  158.         return result;
  159.     },
  160.     getValidPin: function(){
  161.         var retval = { okay: false, pin: "" };
  162.         var topwin = Cc['@mozilla.org/appshell/window-mediator;1'].
  163.             getService(Ci.nsIWindowMediator).
  164.             getMostRecentWindow(null);
  165.         
  166.         if (!topwin) {
  167.             LogWrite("getValidPin: Couldn't find a topwin!");
  168.             throw 4;
  169.         }
  170.         var win = topwin.openDialog(
  171.             "chrome://foxmarks/content/foxmarks-invalidpin.xul", "_blank",
  172.             "chrome,dialog,modal,centerscreen", retval);
  173.         if(!retval.okay) {
  174.             gSettings.setSyncEnabled("passwords", false);
  175.             throw 2;
  176.         }
  177.  
  178.         // WILL- This looks like a mozilla bug, it deallocs result.pin
  179.         //  apparently so you need to assign a new string to it.
  180.         var s = "" + retval.pin;
  181.         return s;
  182.     },
  183.     generateNid: function(login){
  184.         return hex_md5("password|".concat(
  185.                     login.hostname, "|",
  186.                     login.formSubmitURL, "|",
  187.                     login.httpRealm, "|",
  188.                     login.usernameField, "|",
  189.                     login.passwordField, "|",
  190.                     login.username));
  191.     },
  192.     decode: function(node){
  193.         if(!node.private){
  194.             node.private = this.JSON.decode(this.decrypt(node.data));
  195.             if(gSettings.forceGC) {
  196.                 Components.utils.forceGC();
  197.             }
  198.         }
  199.         return node.private;
  200.     },
  201.     handleNidConflict: function(lnode, snode){
  202.         var litem = this.decode(lnode);
  203.         var sitem = this.decode(snode);
  204.         if(litem.password == sitem.password)
  205.             return "same";
  206.         else {
  207.             var result = this._conflictDialog(sitem, litem); 
  208.             switch(result){
  209.                 case 2:
  210.                     return "server";
  211.                 case 1:
  212.                     return "local";
  213.                 default:
  214.                     return "cancel";
  215.             }
  216.         }
  217.         return "cancel";;
  218.     },
  219.     _buildList: function(doEncrypt, includeCt, funcDone){
  220.         var mgr = this.Cc["@mozilla.org/login-manager;1"]
  221.             .getService(this.Ci.nsILoginManager);
  222.  
  223.         var a = mgr.getAllLogins({});
  224.         var ct = a.length;
  225.         var self = this;
  226.         var size = 0;
  227.         //dump("_buildList\n");
  228.         NiceProcess(
  229.             {  ctr: 0,
  230.                a: a,
  231.                len: a.length,
  232.                size: 0,
  233.                result: {},
  234.                done: function(){ return this.ctr >= this.len;}
  235.             },
  236.             function(iter){
  237.                 var login = iter.a[iter.ctr];
  238.                 if(login.httpRealm != SYNC_REALM &&
  239.                         login.httpRealm != SYNC_REALM_PIN &&
  240.                         login.httpRealm != FOXMARKS_SYNC_REALM &&
  241.                         login.httpRealm != FOXMARKS_SYNC_REALM_PIN){
  242.                     var nid = self.generateNid(login);
  243.                     iter.result[nid] =  {
  244.                         ntype: "password",
  245.                         hostname: login.hostname,
  246.                         formSubmitURL: login.formSubmitURL,
  247.                         httpRealm: login.httpRealm,
  248.                         username: login.username,
  249.                         usernameField: login.usernameField,
  250.                         password: login.password,
  251.                         passwordField: login.passwordField,
  252.                         pnid: NODE_ROOT
  253.                     };
  254.                     if(doEncrypt){
  255.                         iter.result[nid].blob = self.encrypt(
  256.                             iter.result[nid].toJSONString()
  257.                         );
  258.                     }
  259.                     iter.size++;
  260.                 }
  261.                 iter.ctr++;
  262.             },
  263.             function(iter, e){
  264.                 if(includeCt)
  265.                     iter.result.totalLogins = iter.size;
  266.                 funcDone(iter.result, e);
  267.             }
  268.         );
  269.     },
  270.  
  271.     ProvideNodes: function(Caller, AddNode, Complete) {
  272.         var self = this;
  273.         this._buildList(true, false, function(a, e){
  274.             // if buildlist threw an error, just drop out now
  275.             if(e) {
  276.                 Complete.apply(Caller, [e]);
  277.                 return;
  278.             }
  279.             try {
  280.                 var now = new Date();
  281.                 var random_stuff = {
  282.                     password: hex_md5(now.getTime().toString())
  283.                 };
  284.                 var root_node = new Node(
  285.                     NODE_ROOT, 
  286.                     {
  287.                         ntype: "folder", 
  288.                         children: [],
  289.                         private: random_stuff,
  290.                         data: self.encrypt(random_stuff.toJSONString())
  291.                     }
  292.                 );
  293.                 var nid;
  294.                 var obj = {};
  295.                 for(nid in a){
  296.                     if(obj[nid] === undefined){ 
  297.                         var item = a[nid];
  298.                         node = new Node(nid, {
  299.                             ntype: "password",
  300.                             data:  item.blob,
  301.                             private: item,
  302.                             pnid: NODE_ROOT
  303.                         });
  304.                         root_node.children.push(nid);
  305.                         AddNode.apply(Caller, [node]);
  306.                     }
  307.                 }
  308.                 AddNode.apply(Caller, [root_node]);
  309.  
  310.                 Complete.apply(Caller, [0]);
  311.            } catch(e) {
  312.                 if(typeof e != "number"){
  313.                     Components.utils.reportError(e);
  314.                     Complete.apply(Caller, [4]);
  315.                 } else {
  316.                     Complete.apply(Caller, [e]);
  317.                 }
  318.            }
  319.         });
  320.     },
  321.     AcceptNodes: function(ns, callback) {
  322.         var self = this;
  323.         this._buildList(false, false, function(list, e){
  324.             // if buildlist ran into an error, drop out now
  325.             if(e) {
  326.                 callback(e);
  327.                 return;
  328.             }
  329.             var nid, node;
  330.             var mgr = self.Cc["@mozilla.org/login-manager;1"]
  331.                 .getService(self.Ci.nsILoginManager);
  332.  
  333.             var removeList = [];
  334.             // Remove any nids that aren't in ns
  335.             for(nid in list){
  336.                 if(list.hasOwnProperty(nid)){
  337.                     node = ns.Node(nid, false, true);
  338.                     if(node === null){
  339.                         removeList.push(nid);
  340.                     }
  341.                 }
  342.             }
  343.  
  344.             //dump("AcceptNodes\n");
  345.             NiceProcess(
  346.                 {
  347.                     a: removeList,
  348.                     len: removeList.length,
  349.                     ctr: 0,
  350.                     done: function() { return this.ctr >= this.len; }
  351.                 },
  352.                 function(iter){
  353.                     var nid = iter.a[iter.ctr];
  354.                     var item = list[nid];
  355.                     var logins = mgr.findLogins({},
  356.                         item.hostname, 
  357.                         item.formSubmitURL, 
  358.                         item.httpRealm);
  359.                         
  360.                     for(var i = 0; i < logins.length; i++) {
  361.                         if(logins[i].username == item.username) {
  362.                             mgr.removeLogin(logins[i]);
  363.                             break;
  364.                         }
  365.                     }
  366.                     
  367.                     iter.ctr++;
  368.                 },
  369.                 function(oldIter, e){
  370.                     // If we had an error, just drop out now
  371.                     if(e) {
  372.                         callback(e);
  373.                         return;
  374.                     }
  375.                     var node = ns.Node(NODE_ROOT, false, true);
  376.  
  377.                     //dump("AcceptNodes 2\n");
  378.                     NiceProcess(
  379.                         {
  380.                             ctr: 0,
  381.                             children: (node.children || []),
  382.                             done: function() {
  383.                                 return this.ctr >= this.children.length;
  384.                             }
  385.                         },
  386.                         function(iter){
  387.                             var nid = iter.children[iter.ctr];
  388.  
  389.                             // Add it
  390.                             node = ns.Node(nid); 
  391.                             var  item = list[nid];
  392.                             var  loginInfo;
  393.  
  394.                             if(!item){
  395.                                 try {
  396.                                     var nitem = self.decode(node);
  397.                                     loginInfo = self.Cc[
  398.                                         "@mozilla.org/login-manager/loginInfo;1"].
  399.                                         createInstance(self.Ci.nsILoginInfo);
  400.                                     loginInfo.init(
  401.                                         nitem.hostname, nitem.formSubmitURL, 
  402.                                         nitem.httpRealm, 
  403.                                         nitem.username, 
  404.                                         nitem.password,
  405.                                         nitem.usernameField, 
  406.                                         nitem.passwordField);
  407.                                     mgr.addLogin(loginInfo);
  408.                                 }
  409.                                 catch(e){
  410.                                     // Pass cancels up the chain
  411.                                     if(typeof e == "number"){ 
  412.                                         throw e;
  413.                                     } else {
  414.                                         Components.utils.reportError(e);
  415.                                     }
  416.                                 }
  417.                             }
  418.                             // Check for changes
  419.                             else if(node.password != item.password){
  420.                                 var logins = mgr.findLogins({},
  421.                                         item.hostname, 
  422.                                         item.formSubmitURL, 
  423.                                         item.httpRealm);
  424.                                         
  425.                                 for(var i = 0; i < logins.length; i++) {
  426.                                     if(logins[i].username == item.username) {
  427.                                         var nitem = self.decode(node);
  428.                                         loginInfo = self.Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(self.Ci.nsILoginInfo);
  429.                                         loginInfo.init(
  430.                                             nitem.hostname,
  431.                                             nitem.formSubmitURL, 
  432.                                             nitem.httpRealm, 
  433.                                             nitem.username, 
  434.                                             nitem.password,
  435.                                             nitem.usernameField, 
  436.                                             nitem.passwordField
  437.                                         );
  438.                                         mgr.modifyLogin(logins[i], loginInfo);
  439.                                         break;
  440.                                     }
  441.                                 }
  442.                             }
  443.                             iter.ctr++;
  444.  
  445.                         },
  446.                         function(iter, e){
  447.                             e = e || 0;
  448.                             callback(e);
  449.                         }
  450.                     );
  451.                 }
  452.              );
  453.          });
  454.     },
  455.  
  456.     merge: function(dest, source) {
  457.         var snode =source.Node(NODE_ROOT, false, true); 
  458.         this._oldpin = gSettings.pin;
  459.         if(snode == null)
  460.             return;
  461.         var dnode = dest.Node(NODE_ROOT, false, true);
  462.         if(dnode == null){
  463.             var now = new Date();
  464.             var random_stuff = {
  465.                     password: hex_md5(now.getTime().toString())
  466.             };
  467.             dest.Do_insert(NODE_ROOT, {ntype: "folder", children: [],
  468.                 data: this.encrypt(random_stuff.toJSONString())});
  469.             dnode = dest.Node(NODE_ROOT, false, true);
  470.         }
  471.         if(!dnode.data){
  472.             var now = new Date();
  473.             var random_stuff = {
  474.                     password: hex_md5(now.getTime().toString())
  475.             };
  476.             ditem = dest.Node(NODE_ROOT,true);
  477.             delete ditem.private;
  478.             ditem.data = this.encrypt(random_stuff.toJSONString());
  479.         }
  480.         var dlist = dnode.children || [];
  481.         var slist = snode.children || [];
  482.  
  483.         var ctr = slist.length;
  484.  
  485.         while(ctr--){
  486.             var nid = slist[ctr];
  487.             var sitem = source.Node(nid);
  488.             var ditem = dest.Node(nid, false,true);
  489.             if(!ditem){
  490.                 dest.Do_insert(nid, sitem.GetSafeAttrs());
  491.             }
  492.             else {
  493.                 var sdata = this.decode(sitem);
  494.                 var ddata = this.decode(ditem);
  495.                 if(sdata.password != ddata.password){
  496.                     var cResult = this._conflictDialog(ddata, sdata);
  497.                     if(cResult  == 1){
  498.                         ditem = dest.Node(nid,true);
  499.                         delete ditem.private;
  500.                         ditem.data = sitem.data;
  501.                     }
  502.                 }
  503.             }
  504.         }
  505.         delete this._oldpin;
  506.     },
  507.     orderIsImportant: false,
  508.     compareNodes: function(snode, onode, attrs){
  509.         if(!snode.data && onode.data){
  510.             attrs['data'] = onode.data;
  511.             return true;
  512.         }
  513.         else if(!onode.data && snode.data){
  514.             attrs['data'] = snode.data;
  515.             return true;
  516.         }
  517.         else if(snode.data === undefined || onode.data === undefined){
  518.             return false;
  519.         }
  520.         var sdata = this.decode(snode);
  521.         var ddata = this.decode(onode);
  522.         attrs.data = onode.data;
  523.         return sdata.password != ddata.password && snode.nid != NODE_ROOT;
  524.     },
  525.     verifyPin: function(pin, node){
  526.         LogWrite("Verifying PIN (Testing Node)");
  527.         LogWrite("Node Stats: " + (node.data ? node.data.length : "null"));
  528.         var result =  this.encryptor.decrypt(pin, node.data);
  529.         LogWrite("Node Verification: " + (result.indexOf('password') >= 0 ?
  530.             "true" : "false"));
  531.         return result.indexOf('password') >= 0;
  532.     },
  533.     WatchForChanges: function(server) {
  534.         return new PasswordSyncWatcher(server);
  535.     }
  536. };
  537. function PasswordSyncWatcher(server){
  538.     this.server = server;
  539.     this.mgr = new PasswordDatasource();
  540.     this.start();
  541. }
  542.  
  543. PasswordSyncWatcher.prototype = {
  544.     start: function(){
  545.         var os = Cc["@mozilla.org/observer-service;1"].
  546.             getService(Ci.nsIObserverService);
  547.         os.addObserver(this, "foxmarks-checkforpasswordchange", false);
  548.         os.addObserver(this, "foxmarks-watchersuspend", false);
  549.     },
  550.     _suspended: false,
  551.  
  552.     /*
  553.         I took this code from nsLoginManagerPrompter.js
  554.         to make sure we watch the same notify box that the
  555.         loginmanager does.
  556.     */
  557.    _getNotifyBox : function (win) {
  558.        try {
  559.            // Get topmost window, in case we're in a frame.
  560.            var notifyWindow = win.top;
  561.  
  562.            // Some sites pop up a temporary login window, when disappears
  563.            // upon submission of credentials. We want to put the notification
  564.            // bar in the opener window if this seems to be happening.
  565.            if (notifyWindow.opener) {
  566.                var webnav = notifyWindow
  567.                                    .QueryInterface(Ci.nsIInterfaceRequestor)
  568.                                    .getInterface(Ci.nsIWebNavigation);
  569.                var chromeWin = webnav
  570.                                    .QueryInterface(Ci.nsIDocShellTreeItem)
  571.                                    .rootTreeItem
  572.                                    .QueryInterface(Ci.nsIInterfaceRequestor)
  573.                                    .getInterface(Ci.nsIDOMWindow);
  574.                var chromeDoc = chromeWin.document.documentElement;
  575.  
  576.                // Check to see if the current window was opened with chrome
  577.                // disabled, and if so use the opener window. But if the window
  578.                // has been used to visit other pages (ie, has a history),
  579.                // assume it'll stick around and *don't* use the opener.
  580.                if (chromeDoc.getAttribute("chromehidden") &&
  581.                    webnav.sessionHistory.count == 1) {
  582.                    notifyWindow = notifyWindow.opener;
  583.                }
  584.            }
  585.  
  586.  
  587.            // Find the <browser> which contains notifyWindow, by looking
  588.            // through all the open windows and all the <browsers> in each.
  589.            var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  590.                     getService(Ci.nsIWindowMediator);
  591.            var enumerator = wm.getEnumerator("navigator:browser");
  592.            var tabbrowser = null;
  593.            var foundBrowser = null;
  594.  
  595.            while (!foundBrowser && enumerator.hasMoreElements()) {
  596.                var win = enumerator.getNext();
  597.                tabbrowser = win.getBrowser(); 
  598.                foundBrowser = tabbrowser.getBrowserForDocument(
  599.                                                  notifyWindow.document);
  600.            }
  601.  
  602.            // Return the notificationBox associated with the browser.
  603.            // Apparently sometimes the notify box doesn't have this
  604.            // function so we should test for it. Bug #4122
  605.            if (foundBrowser){
  606.                var box = tabbrowser.getNotificationBox(foundBrowser);
  607.                return (box && box.getNotificationWithValue) ? box : null;
  608.            }
  609.  
  610.        } catch (e) {
  611.            // If any errors happen, just assume no notification box.
  612.        }
  613.  
  614.        return null;
  615.    },  
  616.     formsubmit : function (form) {
  617.         var doc = form.ownerDocument;
  618.         var win = doc.defaultView;
  619.         var self = this;
  620.  
  621.         // check if there is a password field
  622.         var ct = form.elements.length;
  623.         var found = false;
  624.         while(ct--){
  625.             if(form.elements[ct].type == "password"){
  626.                 found = true;
  627.                 break;
  628.             }
  629.         }
  630.         if(found){
  631.             this.monitorForChange(win);
  632.         }
  633.         return true;
  634.     },
  635.  
  636.     scheduleChangeTest: function(wait){
  637.         var self = this;
  638.         var callbackSingle = {
  639.             notify: function(){
  640.                 self.checkForChanges();
  641.             }
  642.         };
  643.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  644.         timer.initWithCallback(callbackSingle, wait,
  645.             Ci.nsITimer.TYPE_ONE_SHOT);
  646.     },
  647.     monitorForClose: function(box,type){
  648.         var self = this;
  649.         var timerRepeating;
  650.         var maxCtr = 20;
  651.  
  652.         var callbackWait = {
  653.             notify: function(){
  654.                 if(box.getNotificationWithValue(type) == null){
  655.                     self.checkForChanges();
  656.                     timerRepeating.cancel();
  657.                     }
  658.                 else if(maxCtr-- == 0)
  659.                     timerRepeating.cancel();
  660.             }
  661.         };
  662.         timerRepeating = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  663.         timerRepeating .initWithCallback(callbackWait, 1000,
  664.             Ci.nsITimer.TYPE_REPEATING_SLACK);
  665.     },
  666.     checkNotifyBox: function(){
  667.         var notifyType = "";
  668.         var self = this;
  669.  
  670.         var box = self._getNotifyBox(win);
  671.         if(box){
  672.             notifyType = "password-save";
  673.             if(box.getNotificationWithValue(notifyType) == null){
  674.                 notifyType = "password-change";
  675.                 if(box.getNotificationWithValue(notifyType) == null){
  676.                     notifyType = "";
  677.                 }
  678.             }
  679.         }
  680.  
  681.         return notifyType;
  682.     },
  683.     monitorForChange: function(win){
  684.         var self = this;
  685.         var numTimes = 0;
  686.         var callbackFirst = {
  687.             notify: function(){
  688.                 /* Check if we have a notification.
  689.                     if so, poll it until it closes
  690.                 */
  691.                 numTimes++;
  692.                 var box = self._getNotifyBox(win);
  693.                 var secondTry = false;
  694.                 if(box){
  695.                     var notifyType = "password-save";
  696.                     if(box.getNotificationWithValue(notifyType) == null){
  697.                         notifyType = "password-change";
  698.                         if(box.getNotificationWithValue(notifyType) == null){
  699.                             notifyType = "";
  700.                         }
  701.                     }
  702.                     if(notifyType != ""){
  703.                         self.monitorForClose(box, notifyType);
  704.                     }
  705.                     else
  706.                         self.scheduleChangeTest(8000);
  707.                 }
  708.                 // there are times that the loginManager
  709.                 //  will put up a dialog instead of a notification
  710.                 //  box.  In this case, and when they change a password,
  711.                 //  we'll wait 10 seconds more and then check for changes
  712.                 else {
  713.                     if(numTimes > 4){
  714.                         self.scheduleChangeTest(8000);
  715.                     }
  716.                     else {
  717.                         timer.initWithCallback(callbackFirst, 500,
  718.                             Ci.nsITimer.TYPE_ONE_SHOT);
  719.                     }
  720.                 }
  721.             }
  722.         };
  723.  
  724.         // let things settle for a second and see what happens
  725.         var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  726.         timer.initWithCallback(callbackFirst, 500,
  727.             Ci.nsITimer.TYPE_ONE_SHOT);
  728.     },
  729.     observe: function(subject, topic, data)  { 
  730.         var self = this;
  731.  
  732.         switch(topic){
  733.             case "foxmarks-checkforpasswordchange":
  734.                 LogWrite("Checking for password changes.");
  735.                 this.checkForChanges();
  736.                 return;
  737.             case "foxmarks-watchersuspend":
  738.                 this._suspended = data == "1";
  739.                 return;
  740.         }
  741.     },
  742.     checkForChanges: function(){
  743.         var self = this;
  744.         if(!gSettings.isSyncEnabled("passwords") || !gSettings.haveSynced || self._suspended)
  745.             return;
  746.  
  747.         var funcNotifyChange = function(data){
  748.             var lm = Date.now();
  749.             if (!self.lastModified || lm > self.lastModified && !self._suspended) {
  750.                 self.lastModified = lm;
  751.                 var os = Cc["@mozilla.org/observer-service;1"]
  752.                     .getService(Ci.nsIObserverService);
  753.                 os.notifyObservers(null, "foxmarks-datasourcechanged", 
  754.                     lm + ";passwords");
  755.             }
  756.         };
  757.         this.server.getBaseline("passwords", function(status, ns){
  758.             if(status)
  759.                 return;
  760.             
  761.             var mydata = {
  762.                 totalLogins: 0
  763.             };
  764.             var funcCompareList = function(data, e){
  765.                 // if buildlist returned an error, just drop out silently and hope for the best
  766.                 if(e){
  767.                     return;
  768.                 }
  769.  
  770.                 if(mydata.totalLogins != data.totalLogins){
  771.                     funcNotifyChange(data);
  772.                     return;
  773.                 }
  774.                 for(var field in data){
  775.                     if(data.hasOwnProperty(field)){
  776.                         if(!mydata.hasOwnProperty(field) ||
  777.                             mydata[field].password != data[field].password){
  778.                             funcNotifyChange(data);
  779.                             return;
  780.                         }
  781.                     }
  782.                 }
  783.             };
  784.             ns.OnTree(
  785.                 function(nid){
  786.                     if(nid != NODE_ROOT){
  787.                         mydata.totalLogins++;
  788.                         mydata[nid] = ns.Node(nid).private;
  789.                     }
  790.                 },
  791.                 function(){
  792.                     self.mgr._buildList(false, true, funcCompareList);
  793.                 }
  794.             );
  795.  
  796.         });
  797.  
  798.     }
  799. };
  800.